package com.gitee.kesa4j.unilog.config;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class LogAspect {

    private final static int MAX_LOG_LENGTH = 1024 * 8;
    private static final ParameterNameDiscoverer DISCOVERER = new DefaultParameterNameDiscoverer();

    @Pointcut("execution(public * com.gitee.kesa4j.unilog.demos.web.*Controller.*(..))")
    public void restPort(){
    }

    @Pointcut("execution(public * com.gitee.kesa4j.unilog.demos.openapi.*.*(..))")
    public void openApi(){
    }

    @Around("restPort()")
    public Object aroundRestPort(ProceedingJoinPoint point) throws Throwable {
        try {
            log.info("restAPI: {}#{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
            logParamMap(point,true);
        } catch (Exception e) {
            log.error("e: {}", ExceptionUtils.getRootCauseMessage(e));
        }
        return getObjectAndLogResult(point);
    }

    @Around("openApi()")
    public Object aroundOpenApi(ProceedingJoinPoint point) throws Throwable {
        try {
            log.info("openApi: {}#{}", point.getSignature().getDeclaringTypeName(),point.getSignature().getName());
            logParamMap(point,false);
        } catch (Exception e) {
            log.error("e: {}", ExceptionUtils.getRootCauseMessage(e));
        }
        return getObjectAndLogResult(point);
    }

    private void logParamMap(ProceedingJoinPoint point,boolean isRestApi) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        HashMap<Object, Object> map = Maps.newHashMapWithExpectedSize(8);
        for (int i = 0; i < args.length; i++) {
            MethodParameter parameter = getMethodParameter(method, i);
            if (isRestApi) {
                //跳过路径参数
                if (Objects.nonNull(parameter.getParameterAnnotation(PathVariable.class))) {
                    continue;
                }
            }
            //集合
            if (args[i] instanceof Iterable) {
                if (Objects.nonNull(args[i]) && instanceOfSpecialClass(((Iterable) args[i]).iterator().next())) {
                    map.put(parameter.getParameterName(), args[i]);
                }
            }
            //跳过MultipartFile之外的特殊参数
            if (instanceOfSpecialClass(args[i])) {
                if ((args[i] instanceof MultipartFile)) {
                    MultipartFile file = (MultipartFile) args[i];
                    map.put(parameter.getParameterName(), StringUtils.defaultString(file.getOriginalFilename(), file.getName()));
                }
                continue;
            }
            if (isRestApi) {
                //requestBody直接放入map中
                RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
                if (Objects.nonNull(requestBody) && Objects.nonNull(args[i])) {
                    map.put(parameter.getParameterName(), args[i]);
                    continue;
                }
                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
                if (Objects.nonNull(requestParam)) {
                    Object val = ObjectUtils.defaultIfNull(args[i], requestParam.defaultValue());
                    if (Objects.nonNull(val)) {
                        map.put(parameter.getParameterName(), val);
                    }
                }
            }
        }
        log.info("paramMap: {}", JSON.toJSONString(map));
    }

    private MethodParameter getMethodParameter(Method method, int parameterIndex) {
        MethodParameter parameter = new SynthesizingMethodParameter(method, parameterIndex);
        parameter.initParameterNameDiscovery(DISCOVERER);
        return parameter;
    }

    private Object getObjectAndLogResult(ProceedingJoinPoint point) throws Throwable {
        Object result = point.proceed();
        try {
            //不是null 且不是特殊类时，才能打印
            if (result != null && !instanceOfSpecialClass(result)) {
                String resultJson = JSON.toJSONString(result);
                if (StringUtils.length(resultJson) > MAX_LOG_LENGTH) {
                    log.info("result : {}", resultJson);
                } else {
                    log.info("result: {}", resultJson);
                }
            }
        } catch (Exception e){
            log.error("e: {}", ExceptionUtils.getRootCauseMessage(e));
        }
        return result;
    }

    private boolean instanceOfSpecialClass(Object result) {
        return result instanceof ServletRequest
                || result instanceof RequestAttributes
                || result instanceof ServletResponse
                || result instanceof InputStream
                || result instanceof OutputStream
                //MultipartFile是InputStreamResource的子类
                || result instanceof InputStreamResource
                || result instanceof File;
    }
}